{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Introducing the Keras Sequential API on Vertex AI Platform"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Learning objectives**\n",
    "  1. Build a DNN model using the Keras Sequential API\n",
    "  1. Learn how to use feature columns in a Keras model\n",
    "  1. Learn how to train a model with Keras\n",
    "  1. Learn how to save/load, and deploy a Keras model on GCP\n",
    "  1. Learn how to deploy the Model to Vertex AI and make predictions with the Keras model"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Introduction"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The [Keras sequential API](https://keras.io/models/sequential/) allows you to create Tensorflow models layer-by-layer. This is useful for building most kinds of machine learning models but it does not allow you to create models that share layers, re-use layers or have multiple inputs or outputs. \n",
    "\n",
    "In this lab, we'll see how to build a simple deep neural network model using the Keras sequential api and feature columns. Once we have trained our model, we will deploy it using Vertex AI and see how to call our model for online prediciton.\n",
    "\n",
    "Each learning objective will correspond to a __#TODO__  in this student lab notebook -- try to complete this notebook first and then review the [solution notebook](../solutions/3_keras_sequential_api.ipynb)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "!sudo chown -R jupyter:jupyter /home/jupyter/training-data-analyst"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "!pip3 install google-cloud-bigquery==3.25.0 -U"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "!pip install google-cloud-aiplatform==1.59.0"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "!pip uninstall -y shapely pygeos geopandas\n",
    "# Install specific versions of shapely, pygeos, and geopandas known to be compatible\n",
    "!pip install shapely==1.8.5.post1 pygeos==0.12.0 geopandas==0.10.2"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Note:** Please restart the kernel by clicking **Kernel > Restart Kernel**"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Start by importing the necessary libraries for this lab."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import datetime\n",
    "import os\n",
    "import shutil\n",
    "\n",
    "import numpy as np\n",
    "import pandas as pd\n",
    "import tensorflow as tf\n",
    "from google.cloud import aiplatform\n",
    "from matplotlib import pyplot as plt\n",
    "from tensorflow import keras\n",
    "from tensorflow.keras.callbacks import TensorBoard\n",
    "from tensorflow.keras.layers import Dense, DenseFeatures\n",
    "from tensorflow.keras.models import Sequential\n",
    "\n",
    "print(tf.__version__)\n",
    "%matplotlib inline"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Load raw data"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We will use the taxifare dataset, using the CSV files that we created in the first notebook of this sequence. Those files have been saved into `../data`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "-rw-r--r-- 1 jupyter jupyter 123590 Mar  1 15:09 ../data/taxi-test.csv\n",
      "-rw-r--r-- 1 jupyter jupyter 579055 Mar  1 15:09 ../data/taxi-train.csv\n",
      "-rw-r--r-- 1 jupyter jupyter 123114 Mar  1 15:09 ../data/taxi-valid.csv\n"
     ]
    }
   ],
   "source": [
    "!ls -l ../data/*.csv"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "==> ../data/taxi-test.csv <==\n",
      "6.0,2013-03-27 03:35:00 UTC,-73.977672,40.784052,-73.965332,40.801025,2,0\n",
      "19.3,2012-05-10 18:43:16 UTC,-73.954366,40.778924,-74.004094,40.723104,1,1\n",
      "7.5,2014-05-20 23:09:00 UTC,-73.999165,40.738377,-74.003473,40.723862,2,2\n",
      "12.5,2015-02-23 19:51:31 UTC,-73.9652099609375,40.76948165893555,-73.98949432373047,40.739742279052734,1,3\n",
      "10.9,2011-03-19 03:32:00 UTC,-73.99259,40.742957,-73.989908,40.711053,1,4\n",
      "7.0,2012-09-18 12:51:11 UTC,-73.971195,40.751566,-73.975922,40.756361,1,5\n",
      "19.0,2014-05-20 23:09:00 UTC,-73.998392,40.74517,-73.939845,40.74908,1,6\n",
      "8.9,2012-07-18 08:46:08 UTC,-73.997638,40.756541,-73.973303,40.762019,1,7\n",
      "4.5,2010-07-11 20:39:08 UTC,-73.976738,40.751321,-73.986671,40.74883,1,8\n",
      "7.0,2013-12-12 02:16:40 UTC,-73.985024,40.767537,-73.981273,40.779302,1,9\n",
      "\n",
      "==> ../data/taxi-train.csv <==\n",
      "11.3,2011-01-28 20:42:59 UTC,-73.999022,40.739146,-73.990369,40.717866,1,0\n",
      "7.7,2011-06-27 04:28:06 UTC,-73.987443,40.729221,-73.979013,40.758641,1,1\n",
      "10.5,2011-04-03 00:54:53 UTC,-73.982539,40.735725,-73.954797,40.778388,1,2\n",
      "16.2,2009-04-10 04:11:56 UTC,-74.001945,40.740505,-73.91385,40.758559,1,3\n",
      "33.5,2014-02-24 18:22:00 UTC,-73.993372,40.753382,-73.8609,40.732897,2,4\n",
      "6.9,2011-12-10 00:25:23 UTC,-73.996237,40.721848,-73.989416,40.718052,1,5\n",
      "6.1,2012-09-01 14:30:19 UTC,-73.977048,40.758461,-73.984899,40.744693,2,6\n",
      "9.5,2012-11-08 13:28:07 UTC,-73.969402,40.757545,-73.950049,40.776079,1,7\n",
      "9.0,2014-07-15 11:37:25 UTC,-73.979318,40.760949,-73.95767,40.773724,1,8\n",
      "3.3,2009-11-09 18:06:58 UTC,-73.955675,40.779154,-73.961172,40.772368,1,9\n",
      "\n",
      "==> ../data/taxi-valid.csv <==\n",
      "5.3,2012-01-03 19:21:35 UTC,-73.962627,40.763214,-73.973485,40.753353,1,0\n",
      "25.3,2010-09-27 07:30:15 UTC,-73.965799,40.794243,-73.927134,40.852261,3,1\n",
      "27.5,2015-05-19 00:40:02 UTC,-73.86344146728516,40.76899719238281,-73.96058654785156,40.76129913330078,1,2\n",
      "5.7,2010-04-29 12:28:00 UTC,-73.989255,40.738912,-73.97558,40.749172,1,3\n",
      "11.5,2013-06-23 06:08:09 UTC,-73.99731,40.763735,-73.955657,40.768141,1,4\n",
      "18.0,2014-10-14 18:52:03 UTC,-73.997995,40.761638,-74.008985,40.712442,1,5\n",
      "4.9,2010-04-29 12:28:00 UTC,-73.977315,40.766182,-73.970845,40.761462,5,6\n",
      "32.33,2014-02-24 18:22:00 UTC,-73.985358,40.761352,-73.92427,40.699145,1,7\n",
      "17.0,2015-03-26 02:48:58 UTC,-73.93981170654297,40.846473693847656,-73.97361755371094,40.786983489990234,1,8\n",
      "12.5,2013-04-09 09:39:13 UTC,-73.977323,40.753934,-74.00719,40.741472,1,9\n"
     ]
    }
   ],
   "source": [
    "!head ../data/taxi*.csv"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Use tf.data to read the CSV files"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We wrote these functions for reading data from the csv files above in the [previous notebook](./2a_dataset_api.ipynb)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [],
   "source": [
    "CSV_COLUMNS = [\n",
    "    \"fare_amount\",\n",
    "    \"pickup_datetime\",\n",
    "    \"pickup_longitude\",\n",
    "    \"pickup_latitude\",\n",
    "    \"dropoff_longitude\",\n",
    "    \"dropoff_latitude\",\n",
    "    \"passenger_count\",\n",
    "    \"key\",\n",
    "]\n",
    "LABEL_COLUMN = \"fare_amount\"\n",
    "DEFAULTS = [[0.0], [\"na\"], [0.0], [0.0], [0.0], [0.0], [0.0], [\"na\"]]\n",
    "UNWANTED_COLS = [\"pickup_datetime\", \"key\"]\n",
    "\n",
    "\n",
    "def features_and_labels(row_data):\n",
    "    label = row_data.pop(LABEL_COLUMN)\n",
    "    features = row_data\n",
    "\n",
    "    for unwanted_col in UNWANTED_COLS:\n",
    "        features.pop(unwanted_col)\n",
    "\n",
    "    return features, label\n",
    "\n",
    "\n",
    "def create_dataset(pattern, batch_size=1, mode=\"eval\"):\n",
    "    dataset = tf.data.experimental.make_csv_dataset(\n",
    "        pattern, batch_size, CSV_COLUMNS, DEFAULTS\n",
    "    )\n",
    "\n",
    "    dataset = dataset.map(features_and_labels)\n",
    "\n",
    "    if mode == \"train\":\n",
    "        dataset = dataset.shuffle(buffer_size=1000).repeat()\n",
    "\n",
    "    # take advantage of multi-threading; 1=AUTOTUNE\n",
    "    dataset = dataset.prefetch(1)\n",
    "    return dataset"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Build a simple keras DNN model"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We will use feature columns to connect our raw data to our keras DNN model. Feature columns make it easy to perform common types of feature engineering on your raw data. For example, you can one-hot encode categorical data, create feature crosses, embeddings and more. We'll cover these in more detail later in the course, but if you want to a sneak peak browse the official TensorFlow [feature columns guide](https://www.tensorflow.org/guide/feature_columns).\n",
    "\n",
    "In our case we won't do any feature engineering. However, we still need to create a list of feature columns to specify the numeric values which will be passed on to our model. To do this, we use `tf.feature_column.numeric_column()`\n",
    "\n",
    "We use a python dictionary comprehension to create the feature columns for our model, which is just an elegant alternative to a for loop.\n",
    "\n",
    "**Lab Task #1:** Create a feature column dictionary that we will use when building our deep neural network below. The keys should be the element of the `INPUT_COLS` list, while the values should be numeric feature columns."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [],
   "source": [
    "INPUT_COLS = [\n",
    "    \"pickup_longitude\",\n",
    "    \"pickup_latitude\",\n",
    "    \"dropoff_longitude\",\n",
    "    \"dropoff_latitude\",\n",
    "    \"passenger_count\",\n",
    "]\n",
    "\n",
    "# Create input layer of feature columns\n",
    "# TODO 1\n",
    "feature_columns = # TODO -- Your code here."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Next, we create the DNN model. The Sequential model is a linear stack of layers and when building a model using the Sequential API, you configure each layer of the model in turn. Once all the layers have been added, you compile the model.\n",
    "\n",
    "**Lab Task #2a:** Create a deep neural network using Keras's Sequential API. In the cell below, use the `tf.keras.layers` library to create all the layers for your deep neural network."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "2022-03-01 15:28:04.121614: I tensorflow/core/common_runtime/process_util.cc:146] Creating new thread pool with default inter op setting: 2. Tune using inter_op_parallelism_threads for best performance.\n"
     ]
    }
   ],
   "source": [
    "# Build a keras DNN model using Sequential API\n",
    "# TODO 2a\n",
    "model = # TODO -- Your code here."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Next, to prepare the model for training, you must configure the learning process. This is done using the compile method. The compile method takes three arguments:\n",
    "\n",
    "* An optimizer. This could be the string identifier of an existing optimizer (such as `rmsprop` or `adagrad`), or an instance of the [Optimizer class](https://www.tensorflow.org/versions/r2.0/api_docs/python/tf/keras/optimizers).\n",
    "* A loss function. This is the objective that the model will try to minimize. It can be the string identifier of an existing loss function from the [Losses class](https://www.tensorflow.org/versions/r2.0/api_docs/python/tf/keras/losses) (such as categorical_crossentropy or mse), or it can be a custom objective function.\n",
    "* A list of metrics. For any machine learning problem you will want a set of metrics to evaluate your model. A metric could be the string identifier of an existing metric or a custom metric function.\n",
    "\n",
    "We will add an additional custom metric called `rmse` to our list of metrics which will return the root mean square error.\n",
    "\n",
    "**Lab Task #2b:** Compile the model you created above. Create a custom loss function called `rmse` which computes the root mean squared error between `y_true` and `y_pred`. Pass this function to the model as an evaluation metric. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [],
   "source": [
    "# TODO 2b\n",
    "# Create a custom evalution metric\n",
    "def rmse(y_true, y_pred):\n",
    "    return # TODO -- Your code here.\n",
    "\n",
    "\n",
    "# Compile the keras model\n",
    "# TODO -- Your code here."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Train the model"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "To train your model, Keras provides three functions that can be used:\n",
    " 1. `.fit()` for training a model for a fixed number of epochs (iterations on a dataset).\n",
    " 2. `.fit_generator()` for training a model on data yielded batch-by-batch by a generator\n",
    " 3. `.train_on_batch()` runs a single gradient update on a single batch of data. \n",
    " \n",
    "The `.fit()` function works well for small datasets which can fit entirely in memory. However, for large datasets (or if you need to manipulate the training data on the fly via data augmentation, etc) you will need to use `.fit_generator()` instead. The `.train_on_batch()` method is for more fine-grained control over training and accepts only a single batch of data.\n",
    "\n",
    "The taxifare dataset we sampled is small enough to fit in memory, so can we could use `.fit` to train our model. Our `create_dataset` function above generates batches of training examples, so we could also use `.fit_generator`. In fact, when calling `.fit` the method inspects the data, and if it's a generator (as our dataset is) it will invoke automatically `.fit_generator` for training. \n",
    "\n",
    "We start by setting up some parameters for our training job and create the data generators for the training and validation data.\n",
    "\n",
    "We refer you the the blog post [ML Design Pattern #3: Virtual Epochs](https://medium.com/google-cloud/ml-design-pattern-3-virtual-epochs-f842296de730) for further details on why express the training in terms of `NUM_TRAIN_EXAMPLES` and `NUM_EVALS` and why, in this training code, the number of epochs is really equal to the number of evaluations we perform."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [],
   "source": [
    "TRAIN_BATCH_SIZE = 1000\n",
    "NUM_TRAIN_EXAMPLES = 10000 * 5  # training dataset will repeat, wrap around\n",
    "NUM_EVALS = 50  # how many times to evaluate\n",
    "NUM_EVAL_EXAMPLES = 10000  # enough to get a reasonable sample\n",
    "\n",
    "trainds = create_dataset(\n",
    "    pattern=\"../data/taxi-train*\", batch_size=TRAIN_BATCH_SIZE, mode=\"train\"\n",
    ")\n",
    "\n",
    "evalds = create_dataset(\n",
    "    pattern=\"../data/taxi-valid*\", batch_size=1000, mode=\"eval\"\n",
    ").take(NUM_EVAL_EXAMPLES // 1000)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "There are various arguments you can set when calling the [.fit method](https://www.tensorflow.org/versions/r2.0/api_docs/python/tf/keras/Model#fit). Here `x` specifies the input data which in our case is a `tf.data` dataset returning a tuple of (inputs, targets). The `steps_per_epoch` parameter is used to mark the end of training for a single epoch. Here we are training for NUM_EVALS epochs. Lastly, for the `callback` argument we specify a Tensorboard callback so we can inspect Tensorboard after training. "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Lab Task #3:** In the cell below, you will train your model. First, define the `steps_per_epoch` then train your model using `.fit()`, saving the model training output to a variable called `history`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Epoch 1/50\n",
      "WARNING:tensorflow:Layers in a Sequential model should only have a single input tensor, but we receive a <class 'collections.OrderedDict'> input: OrderedDict([('pickup_longitude', <tf.Tensor 'ExpandDims_4:0' shape=(1000, 1) dtype=float32>), ('pickup_latitude', <tf.Tensor 'ExpandDims_3:0' shape=(1000, 1) dtype=float32>), ('dropoff_longitude', <tf.Tensor 'ExpandDims_1:0' shape=(1000, 1) dtype=float32>), ('dropoff_latitude', <tf.Tensor 'ExpandDims:0' shape=(1000, 1) dtype=float32>), ('passenger_count', <tf.Tensor 'ExpandDims_2:0' shape=(1000, 1) dtype=float32>)])\n",
      "Consider rewriting this model with the Functional API.\n",
      "WARNING:tensorflow:Layers in a Sequential model should only have a single input tensor, but we receive a <class 'collections.OrderedDict'> input: OrderedDict([('pickup_longitude', <tf.Tensor 'ExpandDims_4:0' shape=(1000, 1) dtype=float32>), ('pickup_latitude', <tf.Tensor 'ExpandDims_3:0' shape=(1000, 1) dtype=float32>), ('dropoff_longitude', <tf.Tensor 'ExpandDims_1:0' shape=(1000, 1) dtype=float32>), ('dropoff_latitude', <tf.Tensor 'ExpandDims:0' shape=(1000, 1) dtype=float32>), ('passenger_count', <tf.Tensor 'ExpandDims_2:0' shape=(1000, 1) dtype=float32>)])\n",
      "Consider rewriting this model with the Functional API.\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "2022-03-01 15:28:14.887695: I tensorflow/core/profiler/lib/profiler_session.cc:131] Profiler session initializing.\n",
      "2022-03-01 15:28:14.887748: I tensorflow/core/profiler/lib/profiler_session.cc:146] Profiler session started.\n",
      "2022-03-01 15:28:14.888931: I tensorflow/core/profiler/lib/profiler_session.cc:164] Profiler session tear down.\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "WARNING:tensorflow:Layers in a Sequential model should only have a single input tensor, but we receive a <class 'collections.OrderedDict'> input: OrderedDict([('pickup_longitude', <tf.Tensor 'ExpandDims_4:0' shape=(1000, 1) dtype=float32>), ('pickup_latitude', <tf.Tensor 'ExpandDims_3:0' shape=(1000, 1) dtype=float32>), ('dropoff_longitude', <tf.Tensor 'ExpandDims_1:0' shape=(1000, 1) dtype=float32>), ('dropoff_latitude', <tf.Tensor 'ExpandDims:0' shape=(1000, 1) dtype=float32>), ('passenger_count', <tf.Tensor 'ExpandDims_2:0' shape=(1000, 1) dtype=float32>)])\n",
      "Consider rewriting this model with the Functional API.\n",
      "WARNING:tensorflow:Layers in a Sequential model should only have a single input tensor, but we receive a <class 'collections.OrderedDict'> input: OrderedDict([('pickup_longitude', <tf.Tensor 'ExpandDims_4:0' shape=(1000, 1) dtype=float32>), ('pickup_latitude', <tf.Tensor 'ExpandDims_3:0' shape=(1000, 1) dtype=float32>), ('dropoff_longitude', <tf.Tensor 'ExpandDims_1:0' shape=(1000, 1) dtype=float32>), ('dropoff_latitude', <tf.Tensor 'ExpandDims:0' shape=(1000, 1) dtype=float32>), ('passenger_count', <tf.Tensor 'ExpandDims_2:0' shape=(1000, 1) dtype=float32>)])\n",
      "Consider rewriting this model with the Functional API.\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "2022-03-01 15:28:15.387985: I tensorflow/compiler/mlir/mlir_graph_optimization_pass.cc:185] None of the MLIR Optimization Passes are enabled (registered 2)\n",
      "2022-03-01 15:28:25.559425: I tensorflow/core/kernels/data/shuffle_dataset_op.cc:175] Filling up shuffle buffer (this may take a while): 589 of 1000\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "1/1 [==============================] - ETA: 0s - loss: 86.8337 - rmse: 9.3185 - mse: 86.8337"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "2022-03-01 15:28:32.590728: I tensorflow/core/kernels/data/shuffle_dataset_op.cc:228] Shuffle buffer filled.\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "WARNING:tensorflow:Layers in a Sequential model should only have a single input tensor, but we receive a <class 'collections.OrderedDict'> input: OrderedDict([('pickup_longitude', <tf.Tensor 'ExpandDims_4:0' shape=(1000, 1) dtype=float32>), ('pickup_latitude', <tf.Tensor 'ExpandDims_3:0' shape=(1000, 1) dtype=float32>), ('dropoff_longitude', <tf.Tensor 'ExpandDims_1:0' shape=(1000, 1) dtype=float32>), ('dropoff_latitude', <tf.Tensor 'ExpandDims:0' shape=(1000, 1) dtype=float32>), ('passenger_count', <tf.Tensor 'ExpandDims_2:0' shape=(1000, 1) dtype=float32>)])\n",
      "Consider rewriting this model with the Functional API.\n",
      "WARNING:tensorflow:Layers in a Sequential model should only have a single input tensor, but we receive a <class 'collections.OrderedDict'> input: OrderedDict([('pickup_longitude', <tf.Tensor 'ExpandDims_4:0' shape=(1000, 1) dtype=float32>), ('pickup_latitude', <tf.Tensor 'ExpandDims_3:0' shape=(1000, 1) dtype=float32>), ('dropoff_longitude', <tf.Tensor 'ExpandDims_1:0' shape=(1000, 1) dtype=float32>), ('dropoff_latitude', <tf.Tensor 'ExpandDims:0' shape=(1000, 1) dtype=float32>), ('passenger_count', <tf.Tensor 'ExpandDims_2:0' shape=(1000, 1) dtype=float32>)])\n",
      "Consider rewriting this model with the Functional API.\n",
      "1/1 [==============================] - 19s 19s/step - loss: 86.8337 - rmse: 9.3185 - mse: 86.8337 - val_loss: 111.6214 - val_rmse: 10.5327 - val_mse: 111.6214\n",
      "Epoch 2/50\n",
      "1/1 [==============================] - ETA: 0s - loss: 95.0326 - rmse: 9.7485 - mse: 95.0326"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "2022-03-01 15:28:33.760907: I tensorflow/core/profiler/lib/profiler_session.cc:131] Profiler session initializing.\n",
      "2022-03-01 15:28:33.760942: I tensorflow/core/profiler/lib/profiler_session.cc:146] Profiler session started.\n",
      "2022-03-01 15:28:33.776477: I tensorflow/core/profiler/lib/profiler_session.cc:66] Profiler session collecting data.\n",
      "2022-03-01 15:28:33.794647: I tensorflow/core/profiler/lib/profiler_session.cc:164] Profiler session tear down.\n",
      "2022-03-01 15:28:33.805643: I tensorflow/core/profiler/rpc/client/save_profile.cc:136] Creating directory: ./taxi_trained/train/plugins/profile/2022_03_01_15_28_33\n",
      "\n",
      "2022-03-01 15:28:33.809224: I tensorflow/core/profiler/rpc/client/save_profile.cc:142] Dumped gzipped tool data for trace.json.gz to ./taxi_trained/train/plugins/profile/2022_03_01_15_28_33/tensorflow-2-6-20220301-202936.trace.json.gz\n",
      "2022-03-01 15:28:33.825879: I tensorflow/core/profiler/rpc/client/save_profile.cc:136] Creating directory: ./taxi_trained/train/plugins/profile/2022_03_01_15_28_33\n",
      "\n",
      "2022-03-01 15:28:33.826930: I tensorflow/core/profiler/rpc/client/save_profile.cc:142] Dumped gzipped tool data for memory_profile.json.gz to ./taxi_trained/train/plugins/profile/2022_03_01_15_28_33/tensorflow-2-6-20220301-202936.memory_profile.json.gz\n",
      "2022-03-01 15:28:33.827767: I tensorflow/core/profiler/rpc/client/capture_profile.cc:251] Creating directory: ./taxi_trained/train/plugins/profile/2022_03_01_15_28_33\n",
      "Dumped tool data for xplane.pb to ./taxi_trained/train/plugins/profile/2022_03_01_15_28_33/tensorflow-2-6-20220301-202936.xplane.pb\n",
      "Dumped tool data for overview_page.pb to ./taxi_trained/train/plugins/profile/2022_03_01_15_28_33/tensorflow-2-6-20220301-202936.overview_page.pb\n",
      "Dumped tool data for input_pipeline.pb to ./taxi_trained/train/plugins/profile/2022_03_01_15_28_33/tensorflow-2-6-20220301-202936.input_pipeline.pb\n",
      "Dumped tool data for tensorflow_stats.pb to ./taxi_trained/train/plugins/profile/2022_03_01_15_28_33/tensorflow-2-6-20220301-202936.tensorflow_stats.pb\n",
      "Dumped tool data for kernel_stats.pb to ./taxi_trained/train/plugins/profile/2022_03_01_15_28_33/tensorflow-2-6-20220301-202936.kernel_stats.pb\n",
      "\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "1/1 [==============================] - 1s 631ms/step - loss: 95.0326 - rmse: 9.7485 - mse: 95.0326 - val_loss: 110.7286 - val_rmse: 10.5082 - val_mse: 110.7286\n",
      "Epoch 3/50\n",
      "1/1 [==============================] - 1s 678ms/step - loss: 100.8724 - rmse: 10.0435 - mse: 100.8724 - val_loss: 111.7221 - val_rmse: 10.5569 - val_mse: 111.7221\n",
      "Epoch 4/50\n",
      "1/1 [==============================] - 1s 672ms/step - loss: 69.8122 - rmse: 8.3554 - mse: 69.8122 - val_loss: 109.9716 - val_rmse: 10.4700 - val_mse: 109.9716\n",
      "Epoch 5/50\n",
      "1/1 [==============================] - 1s 817ms/step - loss: 92.2186 - rmse: 9.6031 - mse: 92.2186 - val_loss: 110.9402 - val_rmse: 10.5116 - val_mse: 110.9402\n",
      "Epoch 6/50\n",
      "1/1 [==============================] - 1s 638ms/step - loss: 96.1127 - rmse: 9.8037 - mse: 96.1127 - val_loss: 109.2391 - val_rmse: 10.4255 - val_mse: 109.2391\n",
      "Epoch 7/50\n",
      "1/1 [==============================] - 1s 834ms/step - loss: 79.8012 - rmse: 8.9332 - mse: 79.8012 - val_loss: 110.4092 - val_rmse: 10.4860 - val_mse: 110.4092\n",
      "Epoch 8/50\n",
      "1/1 [==============================] - 1s 600ms/step - loss: 89.8345 - rmse: 9.4781 - mse: 89.8345 - val_loss: 111.6290 - val_rmse: 10.5512 - val_mse: 111.6290\n",
      "Epoch 9/50\n",
      "1/1 [==============================] - 1s 900ms/step - loss: 91.8997 - rmse: 9.5864 - mse: 91.8997 - val_loss: 111.6311 - val_rmse: 10.5625 - val_mse: 111.6311\n",
      "Epoch 10/50\n",
      "1/1 [==============================] - 1s 645ms/step - loss: 83.4510 - rmse: 9.1352 - mse: 83.4510 - val_loss: 110.3672 - val_rmse: 10.4949 - val_mse: 110.3672\n",
      "Epoch 11/50\n",
      "1/1 [==============================] - 1s 902ms/step - loss: 74.3593 - rmse: 8.6232 - mse: 74.3593 - val_loss: 111.6339 - val_rmse: 10.5510 - val_mse: 111.6339\n",
      "Epoch 12/50\n",
      "1/1 [==============================] - 1s 578ms/step - loss: 97.3619 - rmse: 9.8672 - mse: 97.3619 - val_loss: 109.9886 - val_rmse: 10.4762 - val_mse: 109.9886\n",
      "Epoch 13/50\n",
      "1/1 [==============================] - 1s 888ms/step - loss: 113.6113 - rmse: 10.6589 - mse: 113.6113 - val_loss: 111.6013 - val_rmse: 10.5448 - val_mse: 111.6013\n",
      "Epoch 14/50\n",
      "1/1 [==============================] - 1s 818ms/step - loss: 99.7472 - rmse: 9.9874 - mse: 99.7472 - val_loss: 109.1441 - val_rmse: 10.4279 - val_mse: 109.1441\n",
      "Epoch 15/50\n",
      "1/1 [==============================] - 1s 884ms/step - loss: 106.5710 - rmse: 10.3233 - mse: 106.5710 - val_loss: 109.9498 - val_rmse: 10.4673 - val_mse: 109.9498\n",
      "Epoch 16/50\n",
      "1/1 [==============================] - 1s 849ms/step - loss: 92.6466 - rmse: 9.6253 - mse: 92.6466 - val_loss: 110.5050 - val_rmse: 10.5027 - val_mse: 110.5050\n",
      "Epoch 17/50\n",
      "1/1 [==============================] - 1s 883ms/step - loss: 97.7346 - rmse: 9.8861 - mse: 97.7346 - val_loss: 109.3118 - val_rmse: 10.4421 - val_mse: 109.3118\n",
      "Epoch 18/50\n",
      "1/1 [==============================] - 1s 874ms/step - loss: 90.0824 - rmse: 9.4912 - mse: 90.0824 - val_loss: 109.5294 - val_rmse: 10.4339 - val_mse: 109.5294\n",
      "Epoch 19/50\n",
      "1/1 [==============================] - 1s 663ms/step - loss: 96.5796 - rmse: 9.8275 - mse: 96.5796 - val_loss: 109.2576 - val_rmse: 10.4515 - val_mse: 109.2576\n",
      "Epoch 20/50\n",
      "1/1 [==============================] - 1s 867ms/step - loss: 106.1093 - rmse: 10.3009 - mse: 106.1093 - val_loss: 109.1621 - val_rmse: 10.4205 - val_mse: 109.1621\n",
      "Epoch 21/50\n",
      "1/1 [==============================] - 1s 611ms/step - loss: 115.5695 - rmse: 10.7503 - mse: 115.5695 - val_loss: 110.4719 - val_rmse: 10.4980 - val_mse: 110.4719\n",
      "Epoch 22/50\n",
      "1/1 [==============================] - 1s 636ms/step - loss: 98.7798 - rmse: 9.9388 - mse: 98.7798 - val_loss: 110.0630 - val_rmse: 10.4821 - val_mse: 110.0630\n",
      "Epoch 23/50\n",
      "1/1 [==============================] - 1s 687ms/step - loss: 110.3259 - rmse: 10.5036 - mse: 110.3259 - val_loss: 110.9868 - val_rmse: 10.5229 - val_mse: 110.9868\n",
      "Epoch 24/50\n",
      "1/1 [==============================] - 1s 619ms/step - loss: 88.8195 - rmse: 9.4244 - mse: 88.8195 - val_loss: 110.5343 - val_rmse: 10.5075 - val_mse: 110.5343\n",
      "Epoch 25/50\n",
      "1/1 [==============================] - 1s 642ms/step - loss: 110.5837 - rmse: 10.5159 - mse: 110.5837 - val_loss: 112.0123 - val_rmse: 10.5718 - val_mse: 112.0123\n",
      "Epoch 26/50\n",
      "1/1 [==============================] - 1s 687ms/step - loss: 106.7621 - rmse: 10.3326 - mse: 106.7621 - val_loss: 111.0455 - val_rmse: 10.5283 - val_mse: 111.0455\n",
      "Epoch 27/50\n",
      "1/1 [==============================] - 1s 915ms/step - loss: 90.4868 - rmse: 9.5125 - mse: 90.4868 - val_loss: 110.0957 - val_rmse: 10.4810 - val_mse: 110.0957\n",
      "Epoch 28/50\n",
      "1/1 [==============================] - 1s 901ms/step - loss: 94.0185 - rmse: 9.6963 - mse: 94.0185 - val_loss: 111.2865 - val_rmse: 10.5410 - val_mse: 111.2865\n",
      "Epoch 29/50\n",
      "1/1 [==============================] - 1s 623ms/step - loss: 75.6933 - rmse: 8.7002 - mse: 75.6933 - val_loss: 110.0175 - val_rmse: 10.4540 - val_mse: 110.0175\n",
      "Epoch 30/50\n",
      "1/1 [==============================] - 1s 605ms/step - loss: 101.2273 - rmse: 10.0612 - mse: 101.2273 - val_loss: 111.0281 - val_rmse: 10.5105 - val_mse: 111.0281\n",
      "Epoch 31/50\n",
      "1/1 [==============================] - 1s 892ms/step - loss: 96.3029 - rmse: 9.8134 - mse: 96.3029 - val_loss: 110.2343 - val_rmse: 10.4832 - val_mse: 110.2343\n",
      "Epoch 32/50\n",
      "1/1 [==============================] - 1s 787ms/step - loss: 92.0935 - rmse: 9.5965 - mse: 92.0935 - val_loss: 109.1263 - val_rmse: 10.4256 - val_mse: 109.1263\n",
      "Epoch 33/50\n",
      "1/1 [==============================] - 1s 906ms/step - loss: 100.0735 - rmse: 10.0037 - mse: 100.0735 - val_loss: 111.4094 - val_rmse: 10.5500 - val_mse: 111.4094\n",
      "Epoch 34/50\n",
      "1/1 [==============================] - 1s 886ms/step - loss: 84.4506 - rmse: 9.1897 - mse: 84.4506 - val_loss: 109.7607 - val_rmse: 10.4625 - val_mse: 109.7607\n",
      "Epoch 35/50\n",
      "1/1 [==============================] - 1s 877ms/step - loss: 110.5707 - rmse: 10.5153 - mse: 110.5707 - val_loss: 109.5393 - val_rmse: 10.4520 - val_mse: 109.5393\n",
      "Epoch 36/50\n",
      "1/1 [==============================] - 1s 798ms/step - loss: 96.5801 - rmse: 9.8275 - mse: 96.5801 - val_loss: 109.2850 - val_rmse: 10.4388 - val_mse: 109.2850\n",
      "Epoch 37/50\n",
      "1/1 [==============================] - 1s 893ms/step - loss: 97.4446 - rmse: 9.8714 - mse: 97.4446 - val_loss: 111.7907 - val_rmse: 10.5596 - val_mse: 111.7907\n",
      "Epoch 38/50\n",
      "1/1 [==============================] - 1s 872ms/step - loss: 96.4687 - rmse: 9.8218 - mse: 96.4687 - val_loss: 111.3933 - val_rmse: 10.5322 - val_mse: 111.3933\n",
      "Epoch 39/50\n",
      "1/1 [==============================] - 1s 891ms/step - loss: 84.6820 - rmse: 9.2023 - mse: 84.6820 - val_loss: 109.6651 - val_rmse: 10.4643 - val_mse: 109.6651\n",
      "Epoch 40/50\n",
      "1/1 [==============================] - 1s 902ms/step - loss: 83.4023 - rmse: 9.1325 - mse: 83.4023 - val_loss: 110.8658 - val_rmse: 10.5137 - val_mse: 110.8658\n",
      "Epoch 41/50\n",
      "1/1 [==============================] - 1s 906ms/step - loss: 81.7721 - rmse: 9.0428 - mse: 81.7721 - val_loss: 111.3767 - val_rmse: 10.5449 - val_mse: 111.3767\n",
      "Epoch 42/50\n",
      "1/1 [==============================] - 1s 892ms/step - loss: 84.2317 - rmse: 9.1778 - mse: 84.2317 - val_loss: 111.8530 - val_rmse: 10.5637 - val_mse: 111.8530\n",
      "Epoch 43/50\n",
      "1/1 [==============================] - 1s 718ms/step - loss: 92.9797 - rmse: 9.6426 - mse: 92.9797 - val_loss: 111.5728 - val_rmse: 10.5288 - val_mse: 111.5728\n",
      "Epoch 44/50\n",
      "1/1 [==============================] - 1s 676ms/step - loss: 84.8660 - rmse: 9.2123 - mse: 84.8660 - val_loss: 109.9323 - val_rmse: 10.4605 - val_mse: 109.9323\n",
      "Epoch 45/50\n",
      "1/1 [==============================] - 1s 882ms/step - loss: 82.3709 - rmse: 9.0758 - mse: 82.3709 - val_loss: 109.6959 - val_rmse: 10.4442 - val_mse: 109.6959\n",
      "Epoch 46/50\n",
      "1/1 [==============================] - 1s 595ms/step - loss: 89.4825 - rmse: 9.4595 - mse: 89.4825 - val_loss: 110.6735 - val_rmse: 10.4951 - val_mse: 110.6735\n",
      "Epoch 47/50\n",
      "1/1 [==============================] - 1s 674ms/step - loss: 120.4867 - rmse: 10.9766 - mse: 120.4867 - val_loss: 111.3062 - val_rmse: 10.5374 - val_mse: 111.3062\n",
      "Epoch 48/50\n",
      "1/1 [==============================] - 1s 658ms/step - loss: 96.4137 - rmse: 9.8190 - mse: 96.4137 - val_loss: 110.2143 - val_rmse: 10.4781 - val_mse: 110.2143\n",
      "Epoch 49/50\n",
      "1/1 [==============================] - 1s 853ms/step - loss: 79.6127 - rmse: 8.9226 - mse: 79.6127 - val_loss: 111.6886 - val_rmse: 10.5594 - val_mse: 111.6886\n",
      "Epoch 50/50\n",
      "1/1 [==============================] - 1s 681ms/step - loss: 79.2611 - rmse: 8.9029 - mse: 79.2611 - val_loss: 109.5042 - val_rmse: 10.4428 - val_mse: 109.5042\n",
      "CPU times: user 1min 28s, sys: 1min 8s, total: 2min 37s\n",
      "Wall time: 57.2 s\n"
     ]
    }
   ],
   "source": [
    "# TODO 3\n",
    "%time \n",
    "steps_per_epoch = # TODO -- Your code here. \n",
    "\n",
    "LOGDIR = \"./taxi_trained\"\n",
    "history = # TODO -- Your code here. "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### High-level model evaluation"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Once we've run data through the model, we can call `.summary()` on the model to get a high-level summary of our network. We can also plot the training and evaluation curves for the metrics we computed above. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Model: \"sequential\"\n",
      "_________________________________________________________________\n",
      "Layer (type)                 Output Shape              Param #   \n",
      "=================================================================\n",
      "dense_features (DenseFeature multiple                  0         \n",
      "_________________________________________________________________\n",
      "h1 (Dense)                   multiple                  192       \n",
      "_________________________________________________________________\n",
      "h2 (Dense)                   multiple                  264       \n",
      "_________________________________________________________________\n",
      "output (Dense)               multiple                  9         \n",
      "=================================================================\n",
      "Total params: 465\n",
      "Trainable params: 465\n",
      "Non-trainable params: 0\n",
      "_________________________________________________________________\n"
     ]
    }
   ],
   "source": [
    "model.summary()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Running `.fit` (or `.fit_generator`) returns a History object which collects all the events recorded during training. Similar to Tensorboard, we can plot the training and validation curves for the model loss and rmse by accessing these elements of the History object."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<AxesSubplot:>"
      ]
     },
     "execution_count": 11,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "data": {
      "image/png": "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",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "RMSE_COLS = [\"rmse\", \"val_rmse\"]\n",
    "\n",
    "pd.DataFrame(history.history)[RMSE_COLS].plot()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 40,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<AxesSubplot:>"
      ]
     },
     "execution_count": 40,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "data": {
      "image/png": "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",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "LOSS_COLS = [\"loss\", \"val_loss\"]\n",
    "\n",
    "pd.DataFrame(history.history)[LOSS_COLS].plot()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Making predictions with our model"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "To make predictions with our trained model, we can call the [predict method](https://www.tensorflow.org/api_docs/python/tf/keras/Model#predict), passing to it a dictionary of values. The `steps` parameter determines the total number of steps before declaring the prediction round finished. Here since we have just one example, we set `steps=1` (setting `steps=None` would also work). Note, however, that if x is a `tf.data` dataset or a dataset iterator, and steps is set to None, predict will run until the input dataset is exhausted."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "WARNING:tensorflow:Layers in a Sequential model should only have a single input tensor, but we receive a <class 'dict'> input: {'pickup_longitude': <tf.Tensor 'ExpandDims_4:0' shape=(1, 1) dtype=float32>, 'pickup_latitude': <tf.Tensor 'ExpandDims_3:0' shape=(1, 1) dtype=float32>, 'dropoff_longitude': <tf.Tensor 'ExpandDims_1:0' shape=(1, 1) dtype=float32>, 'dropoff_latitude': <tf.Tensor 'ExpandDims:0' shape=(1, 1) dtype=float32>, 'passenger_count': <tf.Tensor 'ExpandDims_2:0' shape=(1, 1) dtype=float32>}\n",
      "Consider rewriting this model with the Functional API.\n",
      "WARNING:tensorflow:Layers in a Sequential model should only have a single input tensor, but we receive a <class 'dict'> input: {'pickup_longitude': <tf.Tensor 'ExpandDims_4:0' shape=(1, 1) dtype=float32>, 'pickup_latitude': <tf.Tensor 'ExpandDims_3:0' shape=(1, 1) dtype=float32>, 'dropoff_longitude': <tf.Tensor 'ExpandDims_1:0' shape=(1, 1) dtype=float32>, 'dropoff_latitude': <tf.Tensor 'ExpandDims:0' shape=(1, 1) dtype=float32>, 'passenger_count': <tf.Tensor 'ExpandDims_2:0' shape=(1, 1) dtype=float32>}\n",
      "Consider rewriting this model with the Functional API.\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "array([[11.051991]], dtype=float32)"
      ]
     },
     "execution_count": 13,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "model.predict(\n",
    "    x={\n",
    "        \"pickup_longitude\": tf.convert_to_tensor([-73.982683]),\n",
    "        \"pickup_latitude\": tf.convert_to_tensor([40.742104]),\n",
    "        \"dropoff_longitude\": tf.convert_to_tensor([-73.983766]),\n",
    "        \"dropoff_latitude\": tf.convert_to_tensor([40.755174]),\n",
    "        \"passenger_count\": tf.convert_to_tensor([3.0]),\n",
    "    },\n",
    "    steps=1,\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Export and deploy our model"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Of course, making individual predictions is not realistic, because we can't expect client code to have a model object in memory. For others to use our trained model, we'll have to export our model to a file, and expect client code to instantiate the model from that exported file. \n",
    "\n",
    "We'll export the model to a TensorFlow SavedModel format. Once we have a model in this format, we have lots of ways to \"serve\" the model, from a web application, from JavaScript, from mobile applications, etc."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "WARNING:tensorflow:Layers in a Sequential model should only have a single input tensor, but we receive a <class 'collections.OrderedDict'> input: OrderedDict([('pickup_longitude', <tf.Tensor 'pickup_longitude:0' shape=(None, 1) dtype=float32>), ('pickup_latitude', <tf.Tensor 'pickup_latitude:0' shape=(None, 1) dtype=float32>), ('dropoff_longitude', <tf.Tensor 'dropoff_longitude:0' shape=(None, 1) dtype=float32>), ('dropoff_latitude', <tf.Tensor 'dropoff_latitude:0' shape=(None, 1) dtype=float32>), ('passenger_count', <tf.Tensor 'passenger_count:0' shape=(None, 1) dtype=float32>)])\n",
      "Consider rewriting this model with the Functional API.\n",
      "WARNING:tensorflow:Layers in a Sequential model should only have a single input tensor, but we receive a <class 'collections.OrderedDict'> input: OrderedDict([('pickup_longitude', <tf.Tensor 'pickup_longitude:0' shape=(None, 1) dtype=float32>), ('pickup_latitude', <tf.Tensor 'pickup_latitude:0' shape=(None, 1) dtype=float32>), ('dropoff_longitude', <tf.Tensor 'dropoff_longitude:0' shape=(None, 1) dtype=float32>), ('dropoff_latitude', <tf.Tensor 'dropoff_latitude:0' shape=(None, 1) dtype=float32>), ('passenger_count', <tf.Tensor 'passenger_count:0' shape=(None, 1) dtype=float32>)])\n",
      "Consider rewriting this model with the Functional API.\n",
      "WARNING:tensorflow:Layers in a Sequential model should only have a single input tensor, but we receive a <class 'collections.OrderedDict'> input: OrderedDict([('pickup_longitude', <tf.Tensor 'inputs_4:0' shape=(None, 1) dtype=float32>), ('pickup_latitude', <tf.Tensor 'inputs_3:0' shape=(None, 1) dtype=float32>), ('dropoff_longitude', <tf.Tensor 'inputs_1:0' shape=(None, 1) dtype=float32>), ('dropoff_latitude', <tf.Tensor 'inputs:0' shape=(None, 1) dtype=float32>), ('passenger_count', <tf.Tensor 'inputs_2:0' shape=(None, 1) dtype=float32>)])\n",
      "Consider rewriting this model with the Functional API.\n",
      "WARNING:tensorflow:Layers in a Sequential model should only have a single input tensor, but we receive a <class 'collections.OrderedDict'> input: OrderedDict([('pickup_longitude', <tf.Tensor 'inputs_4:0' shape=(None, 1) dtype=float32>), ('pickup_latitude', <tf.Tensor 'inputs_3:0' shape=(None, 1) dtype=float32>), ('dropoff_longitude', <tf.Tensor 'inputs_1:0' shape=(None, 1) dtype=float32>), ('dropoff_latitude', <tf.Tensor 'inputs:0' shape=(None, 1) dtype=float32>), ('passenger_count', <tf.Tensor 'inputs_2:0' shape=(None, 1) dtype=float32>)])\n",
      "Consider rewriting this model with the Functional API.\n",
      "WARNING:tensorflow:Layers in a Sequential model should only have a single input tensor, but we receive a <class 'collections.OrderedDict'> input: OrderedDict([('pickup_longitude', <tf.Tensor 'inputs_4:0' shape=(None, 1) dtype=float32>), ('pickup_latitude', <tf.Tensor 'inputs_3:0' shape=(None, 1) dtype=float32>), ('dropoff_longitude', <tf.Tensor 'inputs_1:0' shape=(None, 1) dtype=float32>), ('dropoff_latitude', <tf.Tensor 'inputs:0' shape=(None, 1) dtype=float32>), ('passenger_count', <tf.Tensor 'inputs_2:0' shape=(None, 1) dtype=float32>)])\n",
      "Consider rewriting this model with the Functional API.\n",
      "WARNING:tensorflow:Layers in a Sequential model should only have a single input tensor, but we receive a <class 'collections.OrderedDict'> input: OrderedDict([('pickup_longitude', <tf.Tensor 'inputs_4:0' shape=(None, 1) dtype=float32>), ('pickup_latitude', <tf.Tensor 'inputs_3:0' shape=(None, 1) dtype=float32>), ('dropoff_longitude', <tf.Tensor 'inputs_1:0' shape=(None, 1) dtype=float32>), ('dropoff_latitude', <tf.Tensor 'inputs:0' shape=(None, 1) dtype=float32>), ('passenger_count', <tf.Tensor 'inputs_2:0' shape=(None, 1) dtype=float32>)])\n",
      "Consider rewriting this model with the Functional API.\n",
      "WARNING:tensorflow:Layers in a Sequential model should only have a single input tensor, but we receive a <class 'collections.OrderedDict'> input: OrderedDict([('pickup_longitude', <tf.Tensor 'pickup_longitude:0' shape=(None, 1) dtype=float32>), ('pickup_latitude', <tf.Tensor 'pickup_latitude:0' shape=(None, 1) dtype=float32>), ('dropoff_longitude', <tf.Tensor 'dropoff_longitude:0' shape=(None, 1) dtype=float32>), ('dropoff_latitude', <tf.Tensor 'dropoff_latitude:0' shape=(None, 1) dtype=float32>), ('passenger_count', <tf.Tensor 'passenger_count:0' shape=(None, 1) dtype=float32>)])\n",
      "Consider rewriting this model with the Functional API.\n",
      "WARNING:tensorflow:Layers in a Sequential model should only have a single input tensor, but we receive a <class 'collections.OrderedDict'> input: OrderedDict([('pickup_longitude', <tf.Tensor 'pickup_longitude:0' shape=(None, 1) dtype=float32>), ('pickup_latitude', <tf.Tensor 'pickup_latitude:0' shape=(None, 1) dtype=float32>), ('dropoff_longitude', <tf.Tensor 'dropoff_longitude:0' shape=(None, 1) dtype=float32>), ('dropoff_latitude', <tf.Tensor 'dropoff_latitude:0' shape=(None, 1) dtype=float32>), ('passenger_count', <tf.Tensor 'passenger_count:0' shape=(None, 1) dtype=float32>)])\n",
      "Consider rewriting this model with the Functional API.\n",
      "WARNING:tensorflow:Layers in a Sequential model should only have a single input tensor, but we receive a <class 'collections.OrderedDict'> input: OrderedDict([('pickup_longitude', <tf.Tensor 'pickup_longitude:0' shape=(None, 1) dtype=float32>), ('pickup_latitude', <tf.Tensor 'pickup_latitude:0' shape=(None, 1) dtype=float32>), ('dropoff_longitude', <tf.Tensor 'dropoff_longitude:0' shape=(None, 1) dtype=float32>), ('dropoff_latitude', <tf.Tensor 'dropoff_latitude:0' shape=(None, 1) dtype=float32>), ('passenger_count', <tf.Tensor 'passenger_count:0' shape=(None, 1) dtype=float32>)])\n",
      "Consider rewriting this model with the Functional API.\n",
      "WARNING:tensorflow:Layers in a Sequential model should only have a single input tensor, but we receive a <class 'collections.OrderedDict'> input: OrderedDict([('pickup_longitude', <tf.Tensor 'pickup_longitude:0' shape=(None, 1) dtype=float32>), ('pickup_latitude', <tf.Tensor 'pickup_latitude:0' shape=(None, 1) dtype=float32>), ('dropoff_longitude', <tf.Tensor 'dropoff_longitude:0' shape=(None, 1) dtype=float32>), ('dropoff_latitude', <tf.Tensor 'dropoff_latitude:0' shape=(None, 1) dtype=float32>), ('passenger_count', <tf.Tensor 'passenger_count:0' shape=(None, 1) dtype=float32>)])\n",
      "Consider rewriting this model with the Functional API.\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "2022-03-01 15:29:38.764482: W tensorflow/python/util/util.cc:348] Sets are not currently considered sequences, but this may change in the future, so consider avoiding using them.\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "WARNING:tensorflow:Layers in a Sequential model should only have a single input tensor, but we receive a <class 'collections.OrderedDict'> input: OrderedDict([('pickup_longitude', <tf.Tensor 'inputs/pickup_longitude:0' shape=(None, 1) dtype=float32>), ('pickup_latitude', <tf.Tensor 'inputs/pickup_latitude:0' shape=(None, 1) dtype=float32>), ('dropoff_longitude', <tf.Tensor 'inputs/dropoff_longitude:0' shape=(None, 1) dtype=float32>), ('dropoff_latitude', <tf.Tensor 'inputs/dropoff_latitude:0' shape=(None, 1) dtype=float32>), ('passenger_count', <tf.Tensor 'inputs/passenger_count:0' shape=(None, 1) dtype=float32>)])\n",
      "Consider rewriting this model with the Functional API.\n",
      "WARNING:tensorflow:Layers in a Sequential model should only have a single input tensor, but we receive a <class 'collections.OrderedDict'> input: OrderedDict([('pickup_longitude', <tf.Tensor 'inputs/pickup_longitude:0' shape=(None, 1) dtype=float32>), ('pickup_latitude', <tf.Tensor 'inputs/pickup_latitude:0' shape=(None, 1) dtype=float32>), ('dropoff_longitude', <tf.Tensor 'inputs/dropoff_longitude:0' shape=(None, 1) dtype=float32>), ('dropoff_latitude', <tf.Tensor 'inputs/dropoff_latitude:0' shape=(None, 1) dtype=float32>), ('passenger_count', <tf.Tensor 'inputs/passenger_count:0' shape=(None, 1) dtype=float32>)])\n",
      "Consider rewriting this model with the Functional API.\n",
      "WARNING:tensorflow:Layers in a Sequential model should only have a single input tensor, but we receive a <class 'collections.OrderedDict'> input: OrderedDict([('pickup_longitude', <tf.Tensor 'inputs/pickup_longitude:0' shape=(None, 1) dtype=float32>), ('pickup_latitude', <tf.Tensor 'inputs/pickup_latitude:0' shape=(None, 1) dtype=float32>), ('dropoff_longitude', <tf.Tensor 'inputs/dropoff_longitude:0' shape=(None, 1) dtype=float32>), ('dropoff_latitude', <tf.Tensor 'inputs/dropoff_latitude:0' shape=(None, 1) dtype=float32>), ('passenger_count', <tf.Tensor 'inputs/passenger_count:0' shape=(None, 1) dtype=float32>)])\n",
      "Consider rewriting this model with the Functional API.\n",
      "WARNING:tensorflow:Layers in a Sequential model should only have a single input tensor, but we receive a <class 'collections.OrderedDict'> input: OrderedDict([('pickup_longitude', <tf.Tensor 'inputs/pickup_longitude:0' shape=(None, 1) dtype=float32>), ('pickup_latitude', <tf.Tensor 'inputs/pickup_latitude:0' shape=(None, 1) dtype=float32>), ('dropoff_longitude', <tf.Tensor 'inputs/dropoff_longitude:0' shape=(None, 1) dtype=float32>), ('dropoff_latitude', <tf.Tensor 'inputs/dropoff_latitude:0' shape=(None, 1) dtype=float32>), ('passenger_count', <tf.Tensor 'inputs/passenger_count:0' shape=(None, 1) dtype=float32>)])\n",
      "Consider rewriting this model with the Functional API.\n",
      "WARNING:tensorflow:Layers in a Sequential model should only have a single input tensor, but we receive a <class 'collections.OrderedDict'> input: OrderedDict([('pickup_longitude', <tf.Tensor 'pickup_longitude:0' shape=(None, 1) dtype=float32>), ('pickup_latitude', <tf.Tensor 'pickup_latitude:0' shape=(None, 1) dtype=float32>), ('dropoff_longitude', <tf.Tensor 'dropoff_longitude:0' shape=(None, 1) dtype=float32>), ('dropoff_latitude', <tf.Tensor 'dropoff_latitude:0' shape=(None, 1) dtype=float32>), ('passenger_count', <tf.Tensor 'passenger_count:0' shape=(None, 1) dtype=float32>)])\n",
      "Consider rewriting this model with the Functional API.\n",
      "WARNING:tensorflow:Layers in a Sequential model should only have a single input tensor, but we receive a <class 'collections.OrderedDict'> input: OrderedDict([('pickup_longitude', <tf.Tensor 'pickup_longitude:0' shape=(None, 1) dtype=float32>), ('pickup_latitude', <tf.Tensor 'pickup_latitude:0' shape=(None, 1) dtype=float32>), ('dropoff_longitude', <tf.Tensor 'dropoff_longitude:0' shape=(None, 1) dtype=float32>), ('dropoff_latitude', <tf.Tensor 'dropoff_latitude:0' shape=(None, 1) dtype=float32>), ('passenger_count', <tf.Tensor 'passenger_count:0' shape=(None, 1) dtype=float32>)])\n",
      "Consider rewriting this model with the Functional API.\n",
      "WARNING:tensorflow:Layers in a Sequential model should only have a single input tensor, but we receive a <class 'collections.OrderedDict'> input: OrderedDict([('pickup_longitude', <tf.Tensor 'pickup_longitude:0' shape=(None, 1) dtype=float32>), ('pickup_latitude', <tf.Tensor 'pickup_latitude:0' shape=(None, 1) dtype=float32>), ('dropoff_longitude', <tf.Tensor 'dropoff_longitude:0' shape=(None, 1) dtype=float32>), ('dropoff_latitude', <tf.Tensor 'dropoff_latitude:0' shape=(None, 1) dtype=float32>), ('passenger_count', <tf.Tensor 'passenger_count:0' shape=(None, 1) dtype=float32>)])\n",
      "Consider rewriting this model with the Functional API.\n",
      "WARNING:tensorflow:Layers in a Sequential model should only have a single input tensor, but we receive a <class 'collections.OrderedDict'> input: OrderedDict([('pickup_longitude', <tf.Tensor 'pickup_longitude:0' shape=(None, 1) dtype=float32>), ('pickup_latitude', <tf.Tensor 'pickup_latitude:0' shape=(None, 1) dtype=float32>), ('dropoff_longitude', <tf.Tensor 'dropoff_longitude:0' shape=(None, 1) dtype=float32>), ('dropoff_latitude', <tf.Tensor 'dropoff_latitude:0' shape=(None, 1) dtype=float32>), ('passenger_count', <tf.Tensor 'passenger_count:0' shape=(None, 1) dtype=float32>)])\n",
      "Consider rewriting this model with the Functional API.\n",
      "INFO:tensorflow:Assets written to: ./export/savedmodel/20220301152938/assets\n",
      "INFO:tensorflow:Assets written to: ./export/savedmodel/20220301152938/assets\n"
     ]
    }
   ],
   "source": [
    "OUTPUT_DIR = \"./export/savedmodel\"\n",
    "shutil.rmtree(OUTPUT_DIR, ignore_errors=True)\n",
    "TIMESTAMP = datetime.datetime.now().strftime(\"%Y%m%d%H%M%S\")\n",
    "\n",
    "EXPORT_PATH = os.path.join(OUTPUT_DIR, TIMESTAMP)\n",
    "\n",
    "tf.saved_model.save(model, EXPORT_PATH)  # with default serving function"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {
    "tags": [
     "flake8-noqa-cell"
    ]
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "The given SavedModel SignatureDef contains the following input(s):\n",
      "  inputs['dropoff_latitude'] tensor_info:\n",
      "      dtype: DT_FLOAT\n",
      "      shape: (-1, 1)\n",
      "      name: serving_default_dropoff_latitude:0\n",
      "  inputs['dropoff_longitude'] tensor_info:\n",
      "      dtype: DT_FLOAT\n",
      "      shape: (-1, 1)\n",
      "      name: serving_default_dropoff_longitude:0\n",
      "  inputs['passenger_count'] tensor_info:\n",
      "      dtype: DT_FLOAT\n",
      "      shape: (-1, 1)\n",
      "      name: serving_default_passenger_count:0\n",
      "  inputs['pickup_latitude'] tensor_info:\n",
      "      dtype: DT_FLOAT\n",
      "      shape: (-1, 1)\n",
      "      name: serving_default_pickup_latitude:0\n",
      "  inputs['pickup_longitude'] tensor_info:\n",
      "      dtype: DT_FLOAT\n",
      "      shape: (-1, 1)\n",
      "      name: serving_default_pickup_longitude:0\n",
      "The given SavedModel SignatureDef contains the following output(s):\n",
      "  outputs['output_1'] tensor_info:\n",
      "      dtype: DT_FLOAT\n",
      "      shape: (-1, 1)\n",
      "      name: StatefulPartitionedCall:0\n",
      "Method name is: tensorflow/serving/predict\n",
      "./export/savedmodel/20220301152938\n",
      "./export/savedmodel/20220301152938/assets\n",
      "./export/savedmodel/20220301152938/saved_model.pb\n",
      "./export/savedmodel/20220301152938/variables\n",
      "./export/savedmodel/20220301152938/variables/variables.data-00000-of-00001\n",
      "./export/savedmodel/20220301152938/variables/variables.index\n"
     ]
    }
   ],
   "source": [
    "!saved_model_cli show \\\n",
    "    --tag_set serve \\\n",
    "    --signature_def serving_default \\\n",
    "    --dir {EXPORT_PATH}\n",
    "\n",
    "!find {EXPORT_PATH}\n",
    "os.environ['EXPORT_PATH'] = EXPORT_PATH"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Deploy our model to Vertex AI"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Finally, we will deploy our trained model to Vertex AI and see how we can make online predicitons. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {
    "tags": [
     "flake8-noqa-line-1",
     "flake8-noqa-line-8-E501"
    ]
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "MODEL_DISPLAYNAME: taxifare-kerase-sequential20220301152938\n"
     ]
    }
   ],
   "source": [
    "PROJECT = !gcloud config list --format 'value(core.project)' 2>/dev/null\n",
    "PROJECT = PROJECT[0]\n",
    "BUCKET = PROJECT\n",
    "REGION = \"us-central1\"\n",
    "MODEL_DISPLAYNAME = f\"taxifare-kerase-sequential{TIMESTAMP}\"\n",
    "\n",
    "print(f\"MODEL_DISPLAYNAME: {MODEL_DISPLAYNAME}\")\n",
    "\n",
    "# from https://cloud.google.com/vertex-ai/docs/predictions/pre-built-containers\n",
    "SERVING_CONTAINER_IMAGE_URI = (\n",
    "    \"us-docker.pkg.dev/vertex-ai/prediction/tf2-cpu.2-3:latest\"\n",
    ")\n",
    "\n",
    "os.environ[\"BUCKET\"] = BUCKET\n",
    "os.environ[\"REGION\"] = REGION"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {
    "tags": [
     "flake8-noqa-cell"
    ]
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Creating a new GCS bucket.\n",
      "Here are your current buckets:\n",
      "gs://qwiklabs-gcp-02-beb5d64239ff/\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "Creating gs://qwiklabs-gcp-02-beb5d64239ff/...\n"
     ]
    }
   ],
   "source": [
    "%%bash\n",
    "# Create GCS bucket if it doesn't exist already...\n",
    "exists=$(gcloud storage ls | grep -w gs://${BUCKET}/)\n",    "\n",
    "if [ -n \"$exists\" ]; then\n",
    "    echo -e \"Bucket exists, let's not recreate it.\"\n",
    "else\n",
    "    echo \"Creating a new GCS bucket.\"\n",
    "    gcloud storage buckets create --location ${REGION} gs://${BUCKET}\n",    "    echo \"\\nHere are your current buckets:\"\n",
    "    gcloud storage ls\n",    "fi"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {
    "tags": [
     "flake8-noqa-cell"
    ]
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Copying file://./export/savedmodel/20220301152938/saved_model.pb [Content-Type=application/octet-stream]...\n",
      "Copying file://./export/savedmodel/20220301152938/variables/variables.data-00000-of-00001 [Content-Type=application/octet-stream]...\n",
      "Copying file://./export/savedmodel/20220301152938/variables/variables.index [Content-Type=application/octet-stream]...\n",
      "- [3 files][203.0 KiB/203.0 KiB]                                                \n",
      "Operation completed over 3 objects/203.0 KiB.                                    \n"
     ]
    }
   ],
   "source": [
    "!gcloud storage cp --recursive $EXPORT_PATH gs://$BUCKET/$MODEL_DISPLAYNAME"   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "INFO:google.cloud.aiplatform.models:Creating Model\n",
      "INFO:google.cloud.aiplatform.models:Create Model backing LRO: projects/1072022939500/locations/us-central1/models/4361423977806036992/operations/6959579368809037824\n",
      "INFO:google.cloud.aiplatform.models:Model created. Resource name: projects/1072022939500/locations/us-central1/models/4361423977806036992\n",
      "INFO:google.cloud.aiplatform.models:To use this Model in another session:\n",
      "INFO:google.cloud.aiplatform.models:model = aiplatform.Model('projects/1072022939500/locations/us-central1/models/4361423977806036992')\n"
     ]
    }
   ],
   "source": [
    "uploaded_model = aiplatform.Model.upload(\n",
    "    display_name=MODEL_DISPLAYNAME,\n",
    "    artifact_uri=f\"gs://{BUCKET}/{MODEL_DISPLAYNAME}\",\n",
    "    serving_container_image_uri=SERVING_CONTAINER_IMAGE_URI,\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "INFO:google.cloud.aiplatform.models:Creating Endpoint\n",
      "INFO:google.cloud.aiplatform.models:Create Endpoint backing LRO: projects/1072022939500/locations/us-central1/endpoints/9069449205059158016/operations/2672152523552325632\n",
      "INFO:google.cloud.aiplatform.models:Endpoint created. Resource name: projects/1072022939500/locations/us-central1/endpoints/9069449205059158016\n",
      "INFO:google.cloud.aiplatform.models:To use this Endpoint in another session:\n",
      "INFO:google.cloud.aiplatform.models:endpoint = aiplatform.Endpoint('projects/1072022939500/locations/us-central1/endpoints/9069449205059158016')\n",
      "INFO:google.cloud.aiplatform.models:Deploying model to Endpoint : projects/1072022939500/locations/us-central1/endpoints/9069449205059158016\n",
      "INFO:google.cloud.aiplatform.models:Deploy Endpoint model backing LRO: projects/1072022939500/locations/us-central1/endpoints/9069449205059158016/operations/8288141258883334144\n",
      "INFO:google.cloud.aiplatform.models:Endpoint model deployed. Resource name: projects/1072022939500/locations/us-central1/endpoints/9069449205059158016\n"
     ]
    }
   ],
   "source": [
    "MACHINE_TYPE = \"e2-standard-2\"\n",
    "\n",
    "endpoint = uploaded_model.deploy(\n",
    "    machine_type=MACHINE_TYPE,\n",
    "    accelerator_type=None,\n",
    "    accelerator_count=None,\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "metadata": {},
   "outputs": [],
   "source": [
    "instance = {\n",
    "    \"pickup_longitude\": -73.982683,\n",
    "    \"pickup_latitude\": 40.742104,\n",
    "    \"dropoff_longitude\": -73.983766,\n",
    "    \"dropoff_latitude\": 40.755174,\n",
    "    \"passenger_count\": 3.0,\n",
    "}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "Prediction(predictions=[[11.0519924]], deployed_model_id='6370227323706277888', explanations=None)"
      ]
     },
     "execution_count": 25,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "endpoint.predict([instance])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Cleanup\n",
    "\n",
    "When deploying a model to an endpoint for online prediction, the minimum `min-replica-count` is 1, and it is charged per node hour. So let's delete the endpoint to reduce unnecessary charges. Before we can delete the endpoint, we first undeploy all attached models... "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "INFO:google.cloud.aiplatform.models:Undeploying Endpoint model: projects/1072022939500/locations/us-central1/endpoints/9069449205059158016\n",
      "INFO:google.cloud.aiplatform.models:Undeploy Endpoint model backing LRO: projects/1072022939500/locations/us-central1/endpoints/9069449205059158016/operations/6923550571790073856\n",
      "INFO:google.cloud.aiplatform.models:Endpoint model undeployed. Resource name: projects/1072022939500/locations/us-central1/endpoints/9069449205059158016\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "<google.cloud.aiplatform.models.Endpoint object at 0x7fada254b790> \n",
       "resource name: projects/1072022939500/locations/us-central1/endpoints/9069449205059158016"
      ]
     },
     "execution_count": 26,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "endpoint.undeploy_all()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "...then delete the endpoint."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "INFO:google.cloud.aiplatform.base:Deleting Endpoint : projects/1072022939500/locations/us-central1/endpoints/9069449205059158016\n",
      "INFO:google.cloud.aiplatform.base:Delete Endpoint  backing LRO: projects/1072022939500/locations/us-central1/operations/8990702800753131520\n",
      "INFO:google.cloud.aiplatform.base:Endpoint deleted. . Resource name: projects/1072022939500/locations/us-central1/endpoints/9069449205059158016\n"
     ]
    }
   ],
   "source": [
    "endpoint.delete()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Copyright 2021 Google Inc. Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License"
   ]
  }
 ],
 "metadata": {
  "environment": {
   "kernel": "python3",
   "name": "tf2-gpu.2-6.m90",
   "type": "gcloud",
   "uri": "gcr.io/deeplearning-platform-release/tf2-gpu.2-6:m90"
  },
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.12"
  },
  "toc-autonumbering": true,
  "toc-showmarkdowntxt": false
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
